
#include "wave/riff_wave.h"
#include "logger.h"

#include <iostream>
#include <sstream>

// only for testmethode flipSecondChannel()
#define SAMPLE_BLOCK_SIZE 2048

using namespace std;

CRiffWave::CRiffWave()
		:mWav(), mFormat(), mData()
{
}

CRiffWave::~CRiffWave()
{
	close();
}

bool CRiffWave::open(const char* pFilename)
{
	if(mWav.is_open())
		return false;
	mWav.open(pFilename, fstream::in | fstream::out | fstream::binary);
	if(mWav.fail())
		return false;
	if(!openWave()) {
		close();
		printl(0, __FILE__, "openWave() failed\n");
		return false;
	}
	printl(0, __FILE__, getWaveInfo().c_str());
	return true;
}

bool CRiffWave::close()
{
	if(!mWav.is_open())
		return false;
	mWav.close();
	if(mWav.fail())
		return false;
	return true;
}

string CRiffWave::getWaveInfo()
{
	stringstream s;
	s << endl;
	s << "formatTag: " << mFormat.formatTag << endl;
	s << "channels: " << mFormat.channels << endl;
	s << "samples per second (per channel): " << mFormat.samplesPerSec << endl;
	s << "bytes per second: " << mFormat.avgBytesPerSec << endl;
	s << "block align (byte per frame): " << bytePerFrame() << endl;
	s << "bits per sample: " << mFormat.bitsPerSample << endl;
	s << endl;
	s << "byte per sample: " << bytePerSample() << endl;
	s << "size of samples (bytes): " << mData.length << endl;
	s << "samples per channel (=frame count): " << frameCount() << endl;
	s << "samples from all channels: " << sampleCount() << endl;
	s << "seconds: "
			<< (static_cast<float>(mData.length)
					/ static_cast<float>(mFormat.avgBytesPerSec))
			<< endl;
	return s.str();
}

unsigned int CRiffWave::sampleCount()
{
	return mData.samples;
}

unsigned int CRiffWave::frameCount()
{
	return mData.length / mFormat.blockAlign;
}

unsigned int CRiffWave::channels()
{
	return mFormat.channels;
}

unsigned int CRiffWave::frameRate()
{
	return mFormat.samplesPerSec;
}

unsigned int CRiffWave::bitsPerSample()
{
	return mFormat.bitsPerSample;
}

unsigned int CRiffWave::bytePerSample()
{
	return mData.bytePerSample;
}

unsigned int CRiffWave::bytePerFrame()
{
	return mFormat.blockAlign;
}

NWaveFormatTag::EFormatTag CRiffWave::getFormatTag()
{
	return static_cast<NWaveFormatTag::EFormatTag>(mFormat.formatTag);
}

bool CRiffWave::begin()
{
	mData.currentIndex = 0;
	if(!mWav.is_open())
		return false;
	return true;
}

unsigned int CRiffWave::getNextSamples
(
		void* pSamples,
		unsigned int pCount,
		unsigned int &pStartOffset
)
{
	unsigned int lReadCount(readSamples(pSamples,
			mData.currentIndex,
			pCount));
	if(lReadCount == 0)
		return 0;
	pStartOffset = mData.currentIndex;
	mData.currentIndex += lReadCount;
	return lReadCount;
}

unsigned int CRiffWave::readSamples
(
		void* pSamples,
		unsigned int pOffset,
		unsigned int pCount
)
{
	if(pCount == 0) {
		return 0;
	}
	if(pOffset >= mData.samples) {
		return 0;
	}
	if(pOffset + pCount > mData.samples) {
		pCount = mData.samples - pOffset;
	}
	//if(mData.bytePerSample != 2) //only 16bit per sample support
	//	return 0;
	mWav.seekg(mData.filePos + static_cast<fstream::pos_type>(pOffset * mData.bytePerSample), ios_base::beg);
	if(mWav.fail())
		return 0;
	mWav.read(reinterpret_cast<char *>(pSamples), pCount * mData.bytePerSample);
	if(mWav.fail())
		return 0;
	return pCount;
}

unsigned int CRiffWave::writeSamples
(
		void* pSamples,
		unsigned int pOffset,
		unsigned int pCount
)
{
	if(pCount == 0) {
		return 0;
	}
	if(pOffset >= mData.samples) {
		return 0;
	}
	if(pOffset + pCount > mData.samples) {
		pCount = mData.samples - pOffset;
	}
	//if(mData.bytePerSample != 2) //only 16bit per sample support
	//	return 0;
	mWav.seekp(mData.filePos + static_cast<fstream::pos_type>(pOffset * mData.bytePerSample), ios_base::beg);
	if(mWav.fail())
		return 0;
	mWav.write(reinterpret_cast<char *>(pSamples), pCount * mData.bytePerSample);
	if(mWav.fail())
		return 0;
	return pCount;
}

bool CRiffWave::flipEverySecondSample()
{
	// file must be successful opened!
	if(!begin())
		return false;
	int16_t lSamples[SAMPLE_BLOCK_SIZE];
	unsigned int lFullCount(0);
	unsigned int lCount;
	unsigned int lOffset(0);
	while((lCount = getNextSamples(lSamples, SAMPLE_BLOCK_SIZE, lOffset)) > 0) {
		for(unsigned int i = 0; i < lCount; i += 2) {
			lSamples[i] += 0x8000;
		}
		if(writeSamples(lSamples, lOffset, lCount) != lCount)
			return false;
		lFullCount += lCount;
	}
	return true;
}

bool CRiffWave::openWave()
{
	char tmp[5];
	uint32_t lChunkSize(0);
	
	if(!mWav.is_open())
		return false;
	
	tmp[4] = '\0';
	
	// === RIFF-Chunk ===
	mWav.read(tmp, 4);
	if(mWav.fail())
		return false;
	string s(tmp);
	if(s.compare("RIFF"))
		return false;
	mWav.read(reinterpret_cast<char *>(&lChunkSize), 4);
	if(mWav.fail())
		return false;
	
	mWav.read(tmp, 4);
	if(mWav.fail())
		return false;
	s = tmp;
	if(s.compare("WAVE"))
		return false;
	
	// === Format-Chunk ===
	mWav.read(tmp, 4);
	if(mWav.fail())
		return false;
	s = tmp;
	if(s.compare("fmt "))
		return false;
	
	mWav.read(reinterpret_cast<char *>(&lChunkSize), 4);
	if(mWav.fail())
		return false;
	//printl(0, __FILE__, "chunk size of format: %u\n", lChunkSize);
	
	mWav.read(reinterpret_cast<char *>(&mFormat.formatTag), 2);
	if(mWav.fail())
		return false;
	//if(mFormat.formatTag != 1) // only support PCM (1 = PCM)
	//	return false;
	mWav.read(reinterpret_cast<char *>(&mFormat.channels), 2);
	if(mWav.fail())
		return false;
	mWav.read(reinterpret_cast<char *>(&mFormat.samplesPerSec), 4);
	if(mWav.fail())
		return false;
	mWav.read(reinterpret_cast<char *>(&mFormat.avgBytesPerSec), 4);
	if(mWav.fail())
		return false;
	mWav.read(reinterpret_cast<char *>(&mFormat.blockAlign), 2);
	if(mWav.fail())
		return false;
	mWav.read(reinterpret_cast<char *>(&mFormat.bitsPerSample), 2);
	if(mWav.fail())
		return false;
	//if(mFormat.bitsPerSample != 16) // only support 16 bits per sample
	//	return false;
	
	// === search Data-Chunk ===
	for(;;) {
		mWav.read(tmp, 4);
		if(mWav.fail())
			return false;
		s = tmp;
		
		uint32_t lLength(0);
		mWav.read(reinterpret_cast<char *>(&lLength), 4);
		if(mWav.fail())
			return false;
		
		if(s.compare("data") == 0) {
			// found data chunk - ALL OK !
			mData.length = lLength;
			mData.filePos = mWav.tellg();
			mData.bytePerSample = mFormat.blockAlign / mFormat.channels;
			mData.samples = lLength / mData.bytePerSample;
			return true;
		}
		
		// jump to next chunk and check for data-chunk
		mWav.seekg(lLength, ios_base::cur);
	}
	return false;
}

